/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package br.gov.cemaden.agent;

import br.gov.cemaden.agent.exception.ArgumentsException;
import br.gov.cemaden.library.dao.pcds.AquisicaoDao;
import br.gov.cemaden.library.dao.pcds.EstacaoDao;
import br.gov.cemaden.library.dao.pcds.RedeDao;
import br.gov.cemaden.library.interfaces.pcds.AquisicaoInterface;
import br.gov.cemaden.library.interfaces.pcds.EstacaoInterface;
import br.gov.cemaden.library.interfaces.pcds.RedeInterface;
import br.gov.cemaden.library.model.Usuario;
import br.gov.cemaden.library.model.pcds.Rede;
import br.gov.cemaden.sendmail.Sendmail;
import br.gov.cemaden.sendmail.exception.SendmailConfigurationException;
import br.gov.cemaden.agent.util.HibernateUtil;
import br.gov.cemaden.library.dao.smp.LogRedeDao;
import br.gov.cemaden.library.model.smp.LogRede;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.mail.MessagingException;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author alan
 */
public class RedeMonitor extends Agent {
    
    private Path file;
    private final Map<Long, Long> sentmails;
    private Long executionPeriod, emailPeriod;
    private Long databaseExceptionPeriod;
    
    private final Logger logger;
    
    public RedeMonitor(){
        
        sentmails = new HashMap<Long, Long>();
        
        // executionPeriod default value is in milliseconds; it is received in seconds and validateArgs method parses the received velue to milliseconds
        executionPeriod = 60 * 1000L;
        // emailPeriod default value is in milliseconds; it is received in seconds and validateArgs method parses the received value to milliseconds
        emailPeriod = 10 * 60 * 1000L;
        // databaseExceptionPeriod = milliseconds
        databaseExceptionPeriod = (System.currentTimeMillis() - emailPeriod);
        
        this.logger = LoggerFactory.getLogger(RedeMonitor.class);
    }
    
    @Override
    public void setup(){
        
        System.out.println("Starting agent " + getAID().getName());
        logger.info("Starting agent " + getAID().getName());

        Object[] args = getArguments();
        if (args != null) {
            validateArgs(args);
        } else {
            throw new ArgumentsException("The agent is expecting at least 1 (one) parameter to run properly.");
        }
        
        addBehaviour(new TickerBehaviour(this, executionPeriod) {
            
            @Override
            protected void onTick() {
                
                logger.debug(
                        String.format("Starting %s for the %d time at %s.", 
                        RedeMonitor.class.getName(), getTickCount(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()))
                );
                
                Session session = null;
                String exception = null;
                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);

                try {
                    session = HibernateUtil.getSessionFactory().openSession();
                } catch (HibernateException ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } catch (ExceptionInInitializerError ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } catch (Throwable ex) {
                    ex.printStackTrace(pw);
                    exception = sw.toString();
                } finally {
                    if (session == null) {
                        if ( (System.currentTimeMillis() - databaseExceptionPeriod) >= emailPeriod){
                            String warning = "Agente: Erro durante a conexão com o banco de dados!!";
                            String body = String.format("%s%nO Agente %s ira tentar conectar-se novamente em %d segundos. "
                                    + "Caso não seja possível, um novo e-mail será enviado ao endereço de e-mail cadastrado.%n%n%s",
                                    warning, getAID().getName(), TimeUnit.MILLISECONDS.toSeconds(emailPeriod), exception);
                            try {
                                new Sendmail(warning, body, getFile()).send(); 
                            } catch (MessagingException ex) {
                                logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                            } catch (SendmailConfigurationException ex) {
                                logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                            } catch (FileNotFoundException ex) {
                                logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                            } catch (IOException ex) {
                                logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                            }
                            databaseExceptionPeriod = System.currentTimeMillis();
                        }
                    }
                }
                
                if(session != null) {
                    
                    try{
                    
                        RedeInterface redeData = new RedeDao(session);
                        EstacaoInterface estacaoData = new EstacaoDao(session);
                        AquisicaoInterface aquisicaoData = new AquisicaoDao(session);

                        List<Rede> redes = redeData.findAll();

                        if(redes != null){

                            for (Rede rede : redes) {                            

                                if(rede.getTempoMaxSemAquisicao() != null){                                

                                    // Calcula datetime limite para aquisição de dados
                                    Date date = new Date(System.currentTimeMillis() - (rede.getTempoMaxSemAquisicao() * 60 * 1000L));                                

                                    // Captura a quantidade de aquisicões para uma rede dado datetime limite
                                    Long quantity = aquisicaoData.countByRede(rede.getIdRede(), date);

                                    // Calcula o percentual de estações que transmitiram dados
                                    Integer perc = (quantity.intValue()*100) / estacaoData.countByRede(rede).intValue();

                                    // Se o percentual mínimo de funcionamento da rede for nulo, considere-o igual a 75
                                    Short min = (rede.getPercMinFuncto() == null) ? 75 : rede.getPercMinFuncto();

                                    // Se o percentual de estações que transmitiram dados for menor que o mínimo cadastrado ...
                                    if( perc < min ){

                                        // Se Map<Long, Long> sentmails não possuir uma rede, inclua-a.
                                        if (!sentmails.containsKey(rede.getIdRede())) {
                                            sentmails.put(rede.getIdRede(), (System.currentTimeMillis() - (emailPeriod * 1000L)));
                                        }

                                        // Se o intervalo para envio de e-mails expirou, execute o código seguinte ...
                                        if ( (System.currentTimeMillis() - sentmails.get(rede.getIdRede())) >= emailPeriod) {

                                            Set<Usuario> usuarios = new HashSet<Usuario>();
                                            Set<String> emails = new HashSet<String>();
                                            for (Usuario usuario : rede.getUsuarios()){
                                                usuarios.add(usuario);
                                                if(usuario.getEmail() != null){
                                                    emails.add(usuario.getEmail());
                                                }
                                            }

                                            String subject = String.format("Monitor de Rede: %s - Quantidade de dados abaixo do esperado", rede.getNome());
                                            String body = String.format("A rede %s transmitiu apenas %d%% do seu potencial nos últimos %d minutos.%n"
                                                    + "O percentual mínimo esperado para essa rede é de %d%%", 
                                                    rede.getNome(), perc, rede.getTempoMaxSemAquisicao(), rede.getPercMinFuncto());

                                            try {
                                                new Sendmail(subject, body, file).setRecipients(emails).send();
                                            } catch (MessagingException ex) {
                                                ex.printStackTrace(pw);
                                                exception = sw.toString();
                                                body = String.format("%s%n%s", body, exception);
                                            } catch (SendmailConfigurationException ex) {
                                                ex.printStackTrace(pw);
                                                exception = sw.toString();
                                                body = String.format("%s%n%s", body, exception);
                                            } catch (FileNotFoundException ex) {
                                                ex.printStackTrace(pw);
                                                exception = sw.toString();
                                                body = String.format("%s%n%s", body, exception);
                                            } catch (IOException ex) {
                                                ex.printStackTrace(pw);
                                                exception = sw.toString();
                                                body = String.format("%s%n%s", body, exception);
                                            }

                                            // Atualiza Map<Long, Long> sentmails
                                            sentmails.put(rede.getIdRede(), System.currentTimeMillis());

                                            // Grava o log de notificação
                                            
                                            LogRede log = new LogRede(rede, body, new Date(), usuarios);    
                                            new LogRedeDao(session).insert(log);

                                        }
                                    } // #Fim - if( perc < min)
                                }
                            } // #Fim - for(Rede rede : redes)
                        } // #Fim - if(redes != null)

                        // Fecha a sessão com o banco de dados
                        session.close();
                    }
                    catch(HibernateException e){
                        logger.error("Exception when trying to execute AplicacaoMonitor", e);
                        e.printStackTrace(pw);
                        exception = sw.toString();
                        try {
                            new Sendmail("Exception when trying to execute RedeMonitor", exception, file).send();
                        } catch (IOException ex) {
                            logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                        } catch (MessagingException ex) {
                            logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                        } catch (SendmailConfigurationException ex) {
                            logger.error("Erro na tentativa de envio de e-mail: %n", ex);
                        }
                    }                    
                } // #Fim - if(session != null)                
            }
        });        
    }
    
    @Override
    protected void takeDown() {
        System.out.println("The agent " + getAID().getName() + " is finishing.");
    }
    
    private void setFile(String filename) throws FileNotFoundException {
        this.file = Paths.get(filename);
        if (Files.notExists(file)) {
            throw new FileNotFoundException("File not found: " + file);
        }
    }

    private Path getFile() {
        return this.file;
    }

    private void validateArgs(Object[] args) {
        if (args[0] != null) {
            try {
                this.setFile(args[0].toString());
            } catch (FileNotFoundException ex) {
                throw new ArgumentsException("The first argument " + args[0].toString() + " was not found.", ex);
            }
        } else {
            throw new ArgumentsException("There is no parameter for the e-mail properties file.");
        }
        if (args.length > 1) {
            executionPeriod = (args[1] != null) ? Long.parseLong(args[1].toString()) * 1000 : executionPeriod;
            if (args.length > 2) {
                if(args[2] != null){
                    Integer seconds = Integer.parseInt(args[2].toString());
                    if(seconds > TimeUnit.MILLISECONDS.toSeconds(emailPeriod)){
                        emailPeriod = seconds * 1000L;
                    }
                    else{
                        logger.error("The value of the third parameter (email period) was changed to " + TimeUnit.MILLISECONDS.toSeconds(emailPeriod) + " seconds.");
                    }
                }
            }
        }
    }
    
}
